package Cinema.Tests;

import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import junit.framework.Assert;
import org.custommonkey.xmlunit.DetailedDiff;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.Difference;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import Utils.XmlParseHelper;
import Cinema.Client;
import Cinema.User;
import Cinema.CinemaEntities.Location;
import Cinema.CinemaEntities.Movie;
import Cinema.CinemaEntities.Order;
import Cinema.CinemaEntities.Show;
import Cinema.CinemaEntities.Site;
import Cinema.CinemaEntities.Theater;
import Cinema.DataLayer.IDataManager;
import Exceptions.BadArgumentException;
import Exceptions.CinemaException;
import Exceptions.DataAccessException;
import Exceptions.ObjectAlreadyExist;
import Exceptions.OperationCanceledException;
import Exceptions.OrderFailed;
import Exceptions.PaymentFailed;
import Exceptions.ShowExpierd;
import Exceptions.WrongXMLElement;

public class ClientTest implements IXmlParsableTest{

	IDataManager datamannager = new fakeDataMannager();
	final int price=5;

	@Test
	public final void test_getSites_expectACallToDataMannager() {
		Client c = new Client("user", "email", datamannager);
		assertTrue(datamannager.getSites() == c.getSites());
	}

	@Test
	public final void test_passwordGeneration_createLotOfClients_AllUsersDifferInPassword() {
		int amount = 300;
		ArrayList<Client> array = new ArrayList<Client>();
		for (int i = 0; i < amount; i++) {
			array.add(new Client("user", "email", datamannager));
		}
		for (int i = 0; i < amount; i++) {
			for (int j = i + 1; j < amount; j++) {
				String iPass = array.get(i).getPassword();
				String jPass = array.get(j).getPassword();
				Assert.assertFalse(iPass.equals(jPass));
			}
		}
		return;
	}

	@Test
	public final void test_getOrders_onlyNotExpiredOrdersReturn()
			throws CinemaException {
		TestClient c = new TestClient("tester", "email", datamannager);
		ArrayList<Order> orders = new ArrayList<Order>();
		Order expired = new expiredOrder();
		Order notExpired = new notExpiredOrder();
		orders.add(notExpired);
		orders.add(expired);
		c.setOrders(orders);

		Assert.assertEquals(1, c.getOrders().size());
		Assert.assertTrue(notExpired == c.getOrders().get(0));
	}

	@Test
	public final void test_cancelExpiredOrder_clientThrowsAndDeleteTheOrderFromHisOrders()
			throws CinemaException {
		TestClient c = new TestClient("tester", "email", datamannager);
		ArrayList<Order> orders = new ArrayList<Order>();
		Order expired = new expiredOrder();
		Order notExpired = new notExpiredOrder();
		orders.add(notExpired);
		orders.add(expired);
		c.setOrders(orders);

		try {
			c.cancelOrder(expired);
			fail("should have throwned ShowExpired");
		} catch (ShowExpierd e) {
		}
		Assert.assertEquals(1, c.getOrders().size());
		Assert.assertTrue(notExpired == c.getOrders().get(0));
	}

	@Test
	public final void test_cancelNotExpiredOrder_clientDeleteTheOrderFromHisOrdersAndNoExceptionThrown()
			throws CinemaException {
		TestClient c = new TestClient("tester", "email", datamannager);
		ArrayList<Order> orders = new ArrayList<Order>();
		Order expired = new expiredOrder();
		Order notExpired = new notExpiredOrder();
		orders.add(notExpired);
		orders.add(expired);
		c.setOrders(orders);

		try {
			c.cancelOrder(notExpired);

		} catch (Exception e) {
			fail("should have throwned ShowExpired");
		}
		Assert.assertEquals(1, orders.size());
		Assert.assertTrue(expired == orders.get(0));
	}

	@Test
	public final void test_cancelNotUsersOrder_ExceptionThrown()
			throws CinemaException {
		TestClient c = new TestClient("tester", "email", datamannager);
		Order notExpired = new notExpiredOrder();

		try {
			c.cancelOrder(notExpired);
		} catch (BadArgumentException e) {

		} catch (Exception e) {
			fail("should have throwned BadArgument");
		}
	}

	@Test
	public final void test_tryToOrderPlaces_0seatsAsked_throwBadArgumentException()
			throws CinemaException {
		TestClient c = new TestClient("tester", "email", datamannager);
		Movie m = datamannager.getMovies().get(0);
		Show s = new Show(new Date(), new Theater("123", new Location(0, 0), 5,
				6), m,price,price);
		try {
			c.tryToOrderPlaces(s, 0, 0);
			fail("should have throw BadArgumentException");
		} catch (BadArgumentException e) {
		}
	}

	@Test
	public final void test_tryToOrderPlaces_NegativeseatsAsked_throwBadArgumentException()
			throws CinemaException {
		TestClient c = new TestClient("tester", "email", datamannager);
		Movie m = datamannager.getMovies().get(0);
		Show s = new Show(new Date(), new Theater("123", new Location(0, 0), 1,
				6), m,price,price);
		try {
			c.tryToOrderPlaces(s, 0, -1);
			fail("should have throw BadArgumentException");
		} catch (BadArgumentException e) {
		}
	}

	@Test
	public final void test_tryToOrderPlaces_CantOrderSeats_SortAlterantivesByDistanceToOriginalShow()
			throws CinemaException {
		ArrayList<Show> alternateShows = new ArrayList<Show>();
		TestClient c = new TestClient("tester", "email", datamannager,
				alternateShows);
		Movie m = datamannager.getMovies().get(0);
		Show s = new noSeatsShow(new Date(), new Theater("123", new Location(0,
				0), 1, 6), m);
		c.tryToOrderPlaces(s, 0, 3);
		assertTrue(!alternateShows.isEmpty());
		assertEquals(alternateShows.get(0).getTheater().getName(), "close");
		assertEquals(alternateShows.get(1).getTheater().getName(), "middle");
		assertEquals(alternateShows.get(2).getTheater().getName(), "far");
	}

	@Test
	public final void test_tryToOrderPlaces_possibleToOrder_orderAddedAndCountOfMovieGenreIncreased()
			throws CinemaException {
		TestClient c = new TestClient("tester", "email", datamannager);
		Movie m = datamannager.getMovies().get(0);
		Show s = new withSeatShow(new Date(), new Theater("123", new Location(
				0, 0), 1, 6), m);
		c.tryToOrderPlaces(s, 0, 3);
		assertTrue(c.getOrders().get(0) == c.testOrder);
		assertEquals(c.getPreferences().get(m.getGenre()), (Integer) 1);
		c.tryToOrderPlaces(s, 5, 0);
		assertEquals(c.getPreferences().get(m.getGenre()), (Integer) 2);
	}

	@Test
	public final void test_getMoviesforSite_askForMovies_moviesReturnedInUserPreferedOrder() {
		TestClient c = new TestClient("tester", "email", datamannager);
		Map<String, Integer> preferences = c.getPreferences();
		preferences.put("comedy", 1);
		preferences.put("action", 2);

		ArrayList<Movie> movies = c.getMoviesforSite(datamannager.getSites()
				.get(0));
		assertEquals("count should be 4", 4, movies.size());
		assertEquals("action", movies.get(0).getName());
		assertEquals("comedy", movies.get(1).getName());
		assertEquals("horror", movies.get(2).getGenre());
		assertEquals("horror", movies.get(3).getGenre());
	}

	@Override
	@Test
	public final void test_getXml() throws DataAccessException {

		//create tested object
		TestClient client = new TestClient("Itamar Talmi",
				"itamar.talmi@gmail.com", null);
		client.setPassword("797979");
		Map<String, Integer> prefMap = client.getPreferences();
		prefMap.put("Action", 3);

		//create test doc
		Document testDoc = XmlParseHelper.createNewDocument();
		Element testedElement = client.getXml(testDoc);
		testDoc.appendChild(testedElement);

		Document controlDoc = XmlParseHelper.getDocumentFromFile(".\\tests\\testXml\\ClientTest.xml");
		Diff diff = new Diff(controlDoc, testDoc);

		DetailedDiff dd = new DetailedDiff(diff);

		//for debugging
		@SuppressWarnings("unchecked")
		List<Difference> difList = dd.getAllDifferences();
		for (Difference difference : difList) {
			String discription = difference.getDescription();
			discription.toString();
		}
		assertTrue(diff.similar());

	}

	@Override
	@Test
	public final void test_initFromXml() throws DataAccessException{
		//create control client
		TestClient controlClient = new TestClient("Grisha Khorev", "grisha.khorev@gmail.com", null);
		controlClient.setPassword("121212");
		Map<String, Integer> pref = controlClient.getPreferences();
		pref.put("XXX", 7);

		//create test object
		Document testDoc = XmlParseHelper.getDocumentFromFile(".\\tests\\testXml\\ClientTest2.xml");
		TestClient testClient = null;
		try {
			testClient = new TestClient(testDoc.getDocumentElement());
		} catch (WrongXMLElement e) {
			e.printStackTrace();
		}

		boolean isEq = controlClient.checkEquals(testClient);

		assertTrue(isEq);

	}


	// Fakes

	@Test
	// this method is not used.
	public final void test_getAllMovies_askForAllMovies_moviesReturnedInUserPreferedOrder() {
		TestClient c = new TestClient("tester", "email", datamannager);
		Map<String, Integer> preferences = c.getPreferences();
		preferences.put("horror", 1);
		preferences.put("comedy", 2);

		ArrayList<Movie> movies = c.getAllMovies();
		assertEquals("count should be 4", 4, movies.size());
		assertEquals("comedy", movies.get(0).getName());
		assertEquals("horror", movies.get(1).getGenre());
		assertEquals("horror", movies.get(2).getGenre());
		assertEquals("action", movies.get(3).getName());
	}

	@SuppressWarnings("deprecation")
	@Test
	public final void test_getShowsForMovieInSite_askForShows_onlyShowsInTheSiteReturnedSortedByDate()
			throws BadArgumentException {

		// creating cinema map
		Movie m1 = new Movie("movie1", "movie1");
		Movie m2 = new Movie("movie2", "movie2");
		Movie m3 = new Movie("movie3", "movie3");

		final Theater t1 = new Theater("theater1", new Location(1, 1), 1, 1,
				"city1");
		final Theater t2 = new Theater("theater2", new Location(1, 1), 1, 1,
				"city1");
		Site site1 = new Site("site1", new Location(1, 1), "city1") {
			@Override
			public Set<Theater> getTheaters() {
				HashSet<Theater> res = new HashSet<Theater>();
				res.add(t1);
				res.add(t2);
				return res;
			}
		};

		final Theater t3 = new Theater("theater3", new Location(2, 2), 1, 1,
				"city2");
		final Theater t4 = new Theater("theater4", new Location(2, 2), 1, 1,
				"city2");
		Site site2 = new Site("site2", new Location(1, 1), "city2") {
			@Override
			public Set<Theater> getTheaters() {
				HashSet<Theater> res = new HashSet<Theater>();
				res.add(t3);
				res.add(t4);
				return res;
			}
		};


		Show show1 = new Show(new Date(1900, 1, 1), t1, m1,price,price);
		t1.getShows().add(show1);

		Show show2 = new Show(new Date(2000, 1, 1), t2, m2,price,price);
		t2.getShows().add(show2);

		Show show3 = new Show(new Date(1901, 1, 1), t3, m3,price,price);
		t3.getShows().add(show3);

		Show show4 = new Show(new Date(1899, 1, 1), t4, m3,price,price);
		t4.getShows().add(show4);

		Show show5 = new Show(new Date(2005, 1, 1), t4, m3,price,price);
		t4.getShows().add(show5);

		// testing
		Client c = new Client("user", "email", datamannager);
		assertEquals(0, c.getShowsForMovieInSite(m1, site2).size());
		assertEquals(1, c.getShowsForMovieInSite(m1, site1).size());
		assertEquals(show1, c.getShowsForMovieInSite(m1, site1).get(0));
		assertEquals(3, c.getShowsForMovieInSite(m3, site2).size());
		assertEquals(show4, c.getShowsForMovieInSite(m3, site2).get(0));
		assertEquals(show3, c.getShowsForMovieInSite(m3, site2).get(1));
		assertEquals(show5, c.getShowsForMovieInSite(m3, site2).get(2));
	}

	private abstract class fakeOrder extends Order {
		public fakeOrder() {
			super();
		}

		@Override
		public void CancelOrder() throws ShowExpierd {
			if (IsExpired()) {
				throw new ShowExpierd();
			}
		}
	}

	private class expiredOrder extends fakeOrder {
		@Override
		public boolean IsExpired() {
			return true;
		}
	}

	private class notExpiredOrder extends fakeOrder {
		@Override
		public boolean IsExpired() {
			return false;
		}
	}

	private class fakeSite extends Site {

		@Override
		public Set<Movie> getPresentingMovies() {
			return new HashSet<Movie>(datamannager.getMovies());
		}

		public fakeSite(String siteName, Location siteLocation, String city) {
			super(siteName, siteLocation, city);
		}

		@Override
		public Set<Theater> getTheaters() {

			try {
				HashSet<Theater> res = new HashSet<Theater>();
				res.add(new Theater("1", getLocation(), 5, 5));
				res.add(new Theater("2", getLocation(), 5, 5));
				res.add(new Theater("3", getLocation(), 5, 5));
				return res;
			} catch (Exception e) {
				return null;
			}
		}
	}

	private class fakeDataMannager implements IDataManager {
		ArrayList<Site> sites;
		ArrayList<Movie> movies;

		public fakeDataMannager() {
			sites = new ArrayList<Site>();
			sites.add(new fakeSite("middle", new Location(0, 0), "middle"));
			sites.add(new fakeSite("far", new Location(10, 10), "far"));
			sites.add(new fakeSite("close", new Location(2, 2), "close"));

			movies = new ArrayList<Movie>();
			movies.add(new Movie("horror1", "horror"));
			movies.add(new Movie("horror2", "horror"));
			movies.add(new Movie("action", "action"));
			movies.add(new Movie("comedy", "comedy"));

		}

		@Override
		public void addUser(User newUser)
				throws ObjectAlreadyExist {
			fail();
		}

		@Override
		public Client getUser(String userName, String password) {
			fail();
			return null;
		}

		@Override
		public ArrayList<Movie> getMovies() {
			return movies;
		}

		@Override
		public ArrayList<Site> getSites() {
			return sites;
		}

		@Override
		public ArrayList<Show> getShowsForMovieInSite(Movie movie, Site site) {
			ArrayList<Show> showsForMovieInSite = new ArrayList<Show>();
			for (Theater theater : site.getTheaters()) {
				for (Show show : theater.getShows()) {
					if (show.getMovie()==movie){
						showsForMovieInSite.add(show);
					}
				}
			}
			return showsForMovieInSite;
		}

		@Override
		public ArrayList<Show> getOptionalShowsList(int wantedSeatAmount,
				Movie movie) {
			try {
				ArrayList<Show> result = new ArrayList<Show>();
				result.add(		new Show(new Date(),
									new Theater("far",new Location(0, 10000), 5, 5)
								, movie,price,price));
				result.add(new Show(new Date(), new Theater("middle",
						new Location(-10, 10), 5, 5), movie,price,price));
				result.add(new Show(new Date(), new Theater("close",
						new Location(-1, 0), 5, 5), movie,price,price));
				return result;
			} catch (Exception e) {
			}
			return null;
		}

		@Override
		public ArrayList<Show> GetShowsForMovie(Movie movie) {
			try {
				ArrayList<Show> result = new ArrayList<Show>();
				result.add(new Show(new Date(), new Theater("far",
						new Location(0, 10000), 5, 5), movie,price,price));
				result.add(new Show(new Date(), new Theater("middle",
						new Location(-10, 10), 5, 5), movie,price,price));
				result.add(new Show(new Date(), new Theater("close",
						new Location(-1, 0), 5, 5), movie,price,price));
				return result;
			} catch (Exception e) {
				return null;
			}
		}

		@Override
		public void doBackupNow() {

		}

	}

	/**
	 * this testClient exposes features of client to test them. it make it able
	 * to change behavior and use Stubs/Fakes. it does not change the mail
	 * functionality of client, so we can use it form tests.
	 */
	private class TestClient extends Client {

		Order testOrder;

		/**
		 * next feature give us control over the hashcode to contorl order in
		 * hash collections.
		 */
		public Integer myHashCode = null;

		@Override
		public int hashCode() {
			if (myHashCode == null) {
				return super.hashCode();
			}
			return myHashCode;
		};

		@Override
		protected Order makeOrder(Show show, int numOfAdultSeats,
				int numOfChildSeats) throws OperationCanceledException,
				OrderFailed, PaymentFailed {
			testOrder = new notExpiredOrder();
			return testOrder;
		}

		public void setOrders(ArrayList<Order> orders) {
			clientOrders = orders;
		}

		public TestClient(String userName, String email,
				IDataManager dataMannager) {
			super(userName, email, dataMannager);
		}

		ArrayList<Show> saveHereAlternatives;

		public TestClient(String userName, String email,
				IDataManager dataMannager, ArrayList<Show> saveHereAlternatives) {
			super(userName, email, dataMannager);
			this.saveHereAlternatives = saveHereAlternatives;
		}

		public TestClient(Element element) throws WrongXMLElement{
			super(element);
		}

		public Map<String, Integer> getPreferences() {
			return previewsOrderdMoviesType;
		}

		public void setPassword(String pass) {
			password = pass;
		}

		@Override
		protected void showAlternativesUI(Movie movie, ArrayList<Show> shows,
				int numOfAdultSeats, int numOfChildSeats) {
			for (Show show : shows) {
				saveHereAlternatives.add(show);
			}
		}
}

	private class noSeatsShow extends Show {

		public noSeatsShow(Date date, Theater theater, Movie movie) {
			super(date, theater, movie,price,price);
		}

		@Override
		public Boolean checkOrderPossibility(int numberOfSeats) {
			return false;
		}

	}

	private class withSeatShow extends Show {

		public withSeatShow(Date date, Theater theater, Movie movie) {
			super(date, theater, movie,price,price);
		}

		@Override
		public Boolean checkOrderPossibility(int numberOfSeats) {
			return true;
		}

	}
}